ImageNet Designer - Coding Tutorial
Compilation (with MinGW in the DOS console) # Open a console (Press "Windows-Key + R" and type "cmd" in the popup-Window) # Navigate to the source code folder (Hint: Use Total Commander and Select from the Commands menu "Open command prompt window") C:\Programme\ImageNets\build\libImageNetExternal\MinGW # Compile with this command in the console: make -f libImageNetExternal.mak all # Test: Start the ImageNet Designer and create the block out of your just compiled plugin: Right click on the drawing area of ImageNet Designer and Testing/External/Threshold Recommended Editors for editing the source code # Notepad++ # Eclipse Change the name of the Author of a Block (10 Minutes) # Navigate to the build folder (C:\Program Files\ImageNets\build\libImageNetExternal\MinGW) and open the file CTestBlock.cpp # Change the text of m_Author to your name. # Compile with the plugin as described before, restart the ImageNet Designer and create the Block again # Take a look at the Block's Infos. Author should show your name now. Change the title of a Block (5 Minutes) # Navigate to the build folder (C:\Program Files\ImageNets\build\libImageNetExternal\MinGW) and open the file CTestBlock.cpp # Change the text of m_ClassName to TestFunction. # Compile with the plugin as described before, restart the ImageNet Designer and create the Block again # See that the entry in the right click menu changed to TestFunction and the title of the Block also. Add a new property (20 Minutes) # Add an attribute in the CTestBlock.h - const QString WINDOW_SIZE, next to THRESHOLD_PROP # Initialize the new attribute in CTestBlock.cpp in the initialize list (CTestBlock::CTestBlock() : THRESHOLD_PROP("Threshold"), WINDOW_SIZE("Window_Size")) (be careful not to use a space in the string) # In the Constructor of CTestBlock.cpp add another property: m_Properties.AddChildProperty( WINDOW_SIZE, "3" ) # To make the property editable, add a new if-condition in the function editBlock for the new property: ... if condition of other property } else if ( property WINDOW_SIZE ) { bool ok; value = QInputDialog::getInt( 0, "Edit Integer", property, value.toInt(), 0, 255, 1, &ok ); if( ok ) { propertyItem.m_Value = value; //Save the changed data block.process( &imageNet ); //Process the block } return true; //The property was handled } Now you could use this external property in the process function. You have access to your new property with this call: m_PropertiesWINDOW_SIZE.m_Value.toInt(). Instead of toInt, there are also other functions like toString and toDouble, because internally the data is saved in a QVariant. Add new ports (15 Minutes) Add as many input and output ports as you want. Choose a fitting ImageType from ImageNets_Wiki#ImageTypes. # addPortIn( COLOR ); # addPortOut( COLOR ); You have access to the input ports with this call: IplImage* pInputImage = getInputIplImage( 0, //connected input port 0, //layer (normally only layer 0 is used) net ); In the end of the process'function the result is saved with this call: //Save the new image setIplImage( pDstImage, //OpenCV IplImage, which should be saved in the port 0, //port number 0 ); //layer //Set the image type of the output as the same as the input image setImageType( getInputImageType( 0, 0, net ), 0, 0 ); //Copy the input matrices from port 0 to port 0 (layer 0 is always assumed) setMatrices( getInputMatrices( 0, net ), 0 ); Add help texts (15 Minutes) In ImageNets you can write help text directly into the code, which becomes visible if a mouse hovers above a block, its ports or its properties. There are three possibilities to add help texts: # m_ToolTip = '"Your general help text about the block" # m_Properties.AddChildProperty( WINDOW_SIZE, "3", "Property Help Text" ) # addPortIn( COLOR, "Port Help Text" ); Create a new Block (30 Minutes) # Navigate to the build folder and copy the file CTestBlock.cpp to CExternalBlock.cpp, do the same for the respective .h file # Adjust the .cpp and .h of the new copy to fit the new name. (Replace CTestBlock with CExternalBlock) # Change the m_ClassName in the Constructor to "YourFunctionName" # Add #include "CExternalBlock.h" in the Plugin header (CPluginExternal.h). This is needed because the management class CPluginExternal needs to know all blocks. # Add "blocks.push_back( new CExternalBlock )" in the Constructor of the Plugin (CPluginExternal.cpp) # Adjust the Makefile (libImageNetExternal.mak) ## Add to "OBJS= \" "CExternalBlock.o \" ## Add a new rule to make the Block (CExternalBlock.o : CExternalBlock.cpp CExternalBlock.h) (copy it from CTestBlock) ## Add to "clean:" $(RM) CExternalBlock.o Create a new Plugin (45 Minutes) # Copy the whole folder libImageNetExternal in build and rename it to libImageNetSecondExample # call make -f libImageNetExternal.mak clean to clean up all temporary files in libImageNetSecondExample # Rename CPluginExternal.cpp and .h to CPluginSecondExample.cpp and .h. Replace this also in their contents. # Remove CExternalBlock.cpp and .h from the last example # Rename CTestBlock.cpp and .h to CSecondBlock.cpp and .h. Replace this also in their contents. # Rename the Makefile libImageNetExternal.mak to libImageNetSecondExample.mak and change its contents to fit the previously renamed files. # Change CPluginSecondExample.cpp: Q_EXPORT_PLUGIN2(libImageNetSecondExample, ImageNets::CPluginSecondExample); # Change m_CategoryPath in CSecondBlock.cpp to "Testing/SecondExample" and m_ClassName = "Second" (Otherwise there will be two blocks in the same category and the second loaded block will be ignored) Compilation with Rhapsody Adding a new plugin You need a new component in MachineVision/Components and a new package in MachineVision.rpy/GUI/ImageNets/Plugins. The easiest way is to copy an existing component and an existing plugin. Adjust the scope of the component so that only the parts of the new plugin are to be compiled if you select your new component as the active component. In a plugin you can add as many blocks as you want. Adding an edit dialog For simple things use QInputDialog. # Integer # Double # String # StringList (can also be used for Boolean) If you have to edit more complex datatypes you will have to do changes in the component at these locations: # Adjust the scope to add the dialog you want to use (e.g. ImageNets/PropertyEditors/CEditMatrixDialog) # Add to files moc_CEditMatrixDialog (set stereotype: Moc-File, select implementation (so that is becomes *.cpp)) # Adjust Properties - CPP_CG/Linux/PreDependencies: ../../../resources/ImageNets/ui_EditMatrixDialog.h This approach encapsulates the edit dialog into the plugin. This should have the advantage (later) that you can add complex dialogs without having to compile all plugins and the designer. The Structure of Blocks Constructor Specifiy the following # m_ClassName = "Name of your block" # m_CategoryPath = "Fitting Category" # m_Author = Your svn account in capital letters. This is an enum specified in the root of the ImageNet package. # m_ToolTip = "Explanation of what your block should do" # InitSettings(); Necessary call of an inherited class to initiate the settings (also called properties) # m_Settings.AddChildProperty( "Property" , "Value" ); Add as many properties as you want always as two strings. The second one can also be numbers inside a string # addPortIn( COLOR, "ToolTip text about this port" ); Setup the imageType of this port and explain the port in the ToolTip. The text will be visible when the mouse hovers above the port. # addPortOut( COLOR, "ToolTip text about this port" ); The same works for outgoing ports. Every time this function is called, the block will get a new outgoing port. Destructor Due to a bug in Rhapsody you might get a compile error. To fix the compile error select the Destructor of the problematic class and change the property from CPP_CG::Operation::Kind = "abstract" to "common". editBlock QString property = QString( propertyItem.m_Property ); QVariant value = propertyItem.m_Value; if( property REF_COORD_SYSTEM ) { bool ok; QStringList StringList; StringList << WORLD_COORD; StringList << CAMERA_COORD; value = QInputDialog::getItem( 0, "QInputDialog::getItem()", property, StringList, StringList.lastIndexOf( value.toString() ), false, &ok ); if( ok && !value.toString().isEmpty() ) { propertyItem.m_Value = value; block.process( &imageNet ); } return true; } return false; Instead of getItem, you can also use # QInputDialog::getInt # QInputDialog::getDouble # QInputDialog::getString getCopy return new CYourClass( *this ); This is necessary due to the "Prototype Programming Paradigm" used in ImageNets. process 1. Check all inputs With these numbers you do not have to write text for debugging. For example: log( "", -1 ). * -1: input image is missing * -2: preconditions are not fulfilled IplImage* pInputImage = getInputIplImage( 0, 0, net ); if( pInputImage 0 ) { log( "", -1 ); return; } 2. Initialize outputs IplImage* pResultImage = cvCloneImage( pInputImage ); 3. Do the processing (you can use the properties here) cvSmooth( pInputImage, pResultImage, CV_GAUSSIAN, 0, 0, m_SettingsSIGMA.m_Value.toDouble() ); 4. Save the outputs setImage( pResultImage, COLOR, 0, 0 ); 5. Clean up temporary images and matrices (but do not clean up images or matrices which you saved before) //cvReleaseImage( &pTempImage ); //This makes sense only if you have created temporary images. //cvReleaseMat( &pFrameInverted ); //This makes sense only if you have created temporary matrices. Useful functions Important Member Variables of CLayer * 1 ImageType (ImageNets_Wiki#ImageTypes) * 1 OpenCV Image IplImage (See OpenCV Documentation) * vector of OpenCV Matrix CvMat * vector of ImageNet-Bodies (Cuboid, Cylinder, Sphere) * 1 OpenCV Sequence CvSeq (and additionally CvMemStorage) All of the following functions do not copy or allocate any data. From every block you can call the following functions (CBlock calls CPort, which calls CLayer): Images getInputIplImage( inputPort, layer, imageNet ) returns a pointer to the image of the connected block setIplImage( IplImage*, outputPort, layer ) saves an image and deletes the old one Matrices getInputMat( inputPort, layer, matrix, imageNet ) returns a pointer to a matrix from a left connected block addMat( CvMat*, outputPort, layer ) adds a matrix pointer (you have to allocate the memory yourself) getMat( outputPort, layer, matrix ) returns a pointer to an own matrix clearMatrices( outputPort, layer ) deletes all matrices of a CLayer ImageTypes setImageType( imageType, outputPort, layer ) Noise Generally, you can call a noise function, which can return gaussian noise. INCALC::noise( double sigma );